#include "read.h"

#include <cassert> // assert
#include <limits> // std::numeric_limits

#include <unistd.h> // read pread
#include <errno.h> // errno E*

#include "simple/support/algorithm/utils.hpp" // distance

namespace simple::io
{

	template <typename Result, typename Read, typename Error>
	Result read_impl(Read read_f, int fd, byte_range data, Error make_error) noexcept
	{
		assert(data.valid());
		assert(data.begin() != nullptr && data.end() != nullptr);

		auto result = read_f(fd, data.begin(), distance(data));
		if(-1 == result)
		{
			switch(errno)
			{
#if EWOULDBLOCK != EAGAIN
				case EWOULDBLOCK: return data.begin();
#endif
				case EAGAIN: return data.begin();
				case ENOMEM: case ENOBUFS: return system_out_of_resources_to_read{errno};
				case ENXIO: return bad_read_from_device{errno};
				case EBADF: return not_read_handle{errno};
				case EISDIR: return handle_is_directory{errno};

				default: return make_error();
			}
		}

		return result == 0 ? nullptr : data.begin() + result;
	}

	template <typename Result, typename Read, typename Error>
	Result read_impl(Read read_f, enum handle handle, byte_range data, Error make_error) noexcept
	{
		return read_impl<Result>(read_f, static_cast<int>(handle), data, make_error);
	}

	template <typename Result, typename Read, typename Error>
	Result read_impl(Read read_f, const unique_handle& handle, byte_range data, Error make_error) noexcept
	{
		assert(handle != nullptr);
		return read_impl<Result>(read_f, static_cast<enum handle>(handle.get()), data, make_error);
	}

	ex_read_result read_only_errors()
	{
		switch(errno)
		{
			case ENOTCONN: return socket_not_connected{errno};
			case ECONNRESET: return socket_connection_reset{errno};
			case ETIMEDOUT: return socket_connection_timeout{errno};
			default: return error{errno};
		}
	};

	ex_read_result read(const unique_handle& handle, byte_range data) noexcept
	{ return read_impl<ex_read_result>(&::read, handle, data, read_only_errors); }

	ex_read_result read(handle handle, byte_range data) noexcept
	{ return read_impl<ex_read_result>(&::read, handle, data, read_only_errors); }

	auto get_pread(std::size_t offset)
	{
		assert(std::numeric_limits<off_t>::max() >= offset);
		return [offset](int fd, void* buf, size_t nbyte)
			{ return ::pread(fd, buf, nbyte, offset); };
	}

	ex_read_offset_result pread_only_errors()
	{
		switch(errno)
		{
			case ESPIPE: return not_seekable{errno};
			default: return error{errno};
		}
	};

	ex_read_offset_result read(const unique_handle& hndl, byte_range bv, std::size_t off) noexcept
	{ return read_impl<ex_read_offset_result>(get_pread(off), hndl, bv, pread_only_errors); }

	ex_read_offset_result read(handle hndl, byte_range bv, std::size_t off) noexcept
	{ return read_impl<ex_read_offset_result>(get_pread(off), hndl, bv, pread_only_errors); }

	ex_read_result read(standard_input_t so, byte_range bv) noexcept
	{ return read_impl<ex_read_result>(&::read, so.value, bv, read_only_errors); }

	ex_read_offset_result read(standard_input_t so, byte_range bv, std::size_t off) noexcept
	{ return read_impl<ex_read_offset_result>(get_pread(off), so.value, bv, pread_only_errors); }

} // namespace simple::io
